// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package server

import (
	"apiote.xyz/p/szczanieckiej/api"
	"apiote.xyz/p/szczanieckiej/config"
	"apiote.xyz/p/szczanieckiej/traffic"
	traffic_errors "apiote.xyz/p/szczanieckiej/traffic/errors"

	"errors"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"

	"golang.org/x/text/language"

	"git.sr.ht/~sircmpwn/go-bare"
)

type ServerError struct {
	code  int
	field string
	value string
	err   error
}

func (e ServerError) Error() string {
	message := ""
	switch e.code {
	case http.StatusBadRequest:
		message = e.value + " not valid as " + e.field
	case http.StatusNotFound:
		message = e.value + " not found as " + e.field
	default:
		message = fmt.Sprintf("error %d", e.code)
		if e.field != "" {
			message += " in field " + e.field
		}
		if e.value != "" {
			message += " with value " + e.value
		}
		if e.err != nil {
			message += ": " + e.err.Error()
		}
	}
	return message
}

func handleFeed(w http.ResponseWriter, r *http.Request, feedID string, cfg config.Config, t *traffic.Traffic, accept map[uint]struct{}) error {
	if _, ok := accept[4]; !ok {
		return ServerError{
			code: http.StatusNotAcceptable,
		}
	}

	timetable, err := traffic.GetTimetable(feedID, t, cfg)
	defer timetable.Close()
	if err != nil {
		return fmt.Errorf("while getting timetable: %w", err)
	}
	_, err = io.Copy(w, timetable)
	if err != nil {
		return fmt.Errorf("while writing: %w", err)
	}
	return nil
}

func handleLocatables(w http.ResponseWriter, r *http.Request, feedNames []string, cfg config.Config, t *traffic.Traffic, accept map[uint]struct{}) error {
	var locatablesSuccess api.LocatablesResponse
	if _, ok := accept[0]; ok {
		locatablesSuccess = api.LocatablesResponseDev{}
	} else if _, ok := accept[3]; ok {
		locatablesSuccess = api.LocatablesResponseV3{}
	} else if _, ok := accept[2]; ok {
		locatablesSuccess = api.LocatablesResponseV2{}
	} else if _, ok := accept[1]; ok {
		locatablesSuccess = api.LocatablesResponseV1{}
	} else {
		return ServerError{
			code: http.StatusNotAcceptable,
		}
	}
	err := r.ParseForm()
	if err != nil {
		return fmt.Errorf("while parsing form: %w", err)
	}
	dateString := r.Form.Get("date")
	lb, err := traffic.ParsePosition(r.Form.Get("lb"))
	if err != nil {
		return ServerError{
			code:  http.StatusBadRequest,
			field: "lb",
			value: r.Form.Get("lb"),
			err:   err,
		}
	}
	rt, err := traffic.ParsePosition(r.Form.Get("rt"))
	if err != nil {
		return ServerError{
			code:  http.StatusBadRequest,
			field: "rt",
			value: r.Form.Get("rt"),
			err:   err,
		}
	}
	for _, feedName := range feedNames {
		versionCode, _, err := parseDate(dateString, feedName, t)
		if err != nil {
			return fmt.Errorf("while parsing date: %w", err)
		}

		context := traffic.Context{
			DataHome: cfg.FeedsPath,
			FeedID:   feedName,
			Version:  versionCode,
		}
		stops, err := traffic.GetStopsIn(lb, rt, context, t)
		if err != nil {
			return fmt.Errorf("while getting stops in bounding box: %w", err)
		}
		vehicles, err := traffic.GetVehiclesIn(lb, rt, context, t)
		if err != nil {
			return fmt.Errorf("while getting vehicles in bounding box: %w", err)
		}
		locatables := []traffic.Locatable{}
		for _, stop := range stops {
			locatables = append(locatables, stop)
		}
		for _, vehicle := range vehicles {
			locatables = append(locatables, vehicle)
		}
		if _, ok := accept[0]; ok {
			locatablesSuccess, err = api.CreateSuccessLocatablesV3(locatables, context, t, locatablesSuccess)
		} else if _, ok := accept[3]; ok {
			locatablesSuccess, err = api.CreateSuccessLocatablesV3(locatables, context, t, locatablesSuccess)
		} else if _, ok := accept[2]; ok {
			locatablesSuccess, err = api.CreateSuccessLocatablesV2(locatables, context, t, locatablesSuccess)
		} else if _, ok := accept[1]; ok {
			locatablesSuccess, err = api.CreateSuccessLocatables(locatables, context, t, locatablesSuccess)
		} else {
			return ServerError{
				code: http.StatusNotAcceptable,
			}
		}
		if err != nil {
			return fmt.Errorf("while creating locatablesSuccess from near locatables: %w", err)
		}
	}
	bytes, err := bare.Marshal(&locatablesSuccess)
	if err != nil {
		return fmt.Errorf("while marshaling: %w", err)
	}
	_, err = w.Write(bytes)
	if err != nil {
		return fmt.Errorf("while writing: %w", err)
	}
	return nil
}

func handleQueryables(w http.ResponseWriter, r *http.Request, feedNames []string, cfg config.Config, t *traffic.Traffic, accept map[uint]struct{}) error {
	var queryablesSuccess api.QueryablesResponse
	if _, ok := accept[0]; ok {
		queryablesSuccess = api.QueryablesResponseDev{}
	} else if _, ok := accept[4]; ok {
		queryablesSuccess = api.QueryablesResponseV4{}
	} else if _, ok := accept[3]; ok {
		queryablesSuccess = api.QueryablesResponseV3{}
	} else if _, ok := accept[2]; ok {
		queryablesSuccess = api.QueryablesResponseV2{}
	} else if _, ok := accept[1]; ok {
		queryablesSuccess = api.QueryablesResponseV1{}
	} else {
		return ServerError{
			code: http.StatusNotAcceptable,
		}
	}
	err := r.ParseForm()
	if err != nil {
		return fmt.Errorf("while parsing form: %w", err)
	}
	query := r.Form.Get("q")
	near := r.Form.Get("near")
	dateString := r.Form.Get("date")
	limitString := r.Form.Get("limit")
	if limitString == "" {
		limitString = "12"
	}
	limit, err := strconv.ParseUint(limitString, 10, 0)
	if err != nil {
		return ServerError{
			code:  http.StatusBadRequest,
			field: "limit",
			value: limitString,
		}
	}
	offsetString := r.Form.Get("offset")
	if offsetString == "" {
		offsetString = "0"
	}
	offset, err := strconv.ParseUint(offsetString, 10, 0)
	if err != nil {
		return ServerError{
			code:  http.StatusBadRequest,
			field: "offset",
			value: offsetString,
		}
	}

	for _, feedName := range feedNames {
		versionCode, _, err := parseDate(dateString, feedName, t)
		if err != nil {
			return fmt.Errorf("while parsing date: %w", err)
		}

		context := traffic.Context{
			DataHome: cfg.FeedsPath,
			FeedID:   feedName,
			Version:  versionCode,
		}

		if near != "" {
			location, err := traffic.ParsePosition(near)
			if err != nil {
				return ServerError{
					code:  http.StatusBadRequest,
					field: "near",
					value: near,
					err:   err,
				}
			}
			stops, err := traffic.GetStopsNear(location, context, t)
			if err != nil {
				return fmt.Errorf("while getting near stops: %w", err)
			}
			items := []traffic.Queryable{}
			for _, stop := range stops {
				items = append(items, stop)
			}
			if _, ok := accept[0]; ok {
				queryablesSuccess, err = api.CreateSuccessQueryablesV4(near, items, context, t, queryablesSuccess, true)
			} else if _, ok := accept[4]; ok {
				queryablesSuccess, err = api.CreateSuccessQueryablesV4(near, items, context, t, queryablesSuccess, true)
			} else if _, ok := accept[3]; ok {
				queryablesSuccess, err = api.CreateSuccessQueryablesV3(near, items, context, t, queryablesSuccess, true)
			} else if _, ok := accept[2]; ok {
				queryablesSuccess, err = api.CreateSuccessQueryablesV2(near, items, context, t, queryablesSuccess, true)
			} else if _, ok := accept[1]; ok {
				queryablesSuccess, err = api.CreateSuccessQueryables(items, context, t, queryablesSuccess, true)
			} else {
				return ServerError{
					code: http.StatusNotAcceptable,
				}
			}
			if err != nil {
				return fmt.Errorf("while creating stopsSuccess from near stops: %w", err)
			}
		} else {
			ix := t.CodeIndexes[feedName][versionCode]
			code := query
			_, exists := ix[code]
			if exists {
				stop, err := traffic.GetStop(code, context, t)
				if err != nil {
					return fmt.Errorf("while getting stop: %w", err)
				}
				if _, ok := accept[0]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryablesV4(query, []traffic.Queryable{stop}, context, t, queryablesSuccess, false)
				} else if _, ok := accept[4]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryablesV4(query, []traffic.Queryable{stop}, context, t, queryablesSuccess, false)
				} else if _, ok := accept[3]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryablesV3(query, []traffic.Queryable{stop}, context, t, queryablesSuccess, false)
				} else if _, ok := accept[2]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryablesV2(query, []traffic.Queryable{stop}, context, t, queryablesSuccess, false)
				} else if _, ok := accept[1]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryables([]traffic.Queryable{stop}, context, t, queryablesSuccess, false)
				} else {
					return ServerError{
						code: http.StatusNotAcceptable,
					}
				}
				if err != nil {
					return fmt.Errorf("while creating stopsSuccess from code: %w", err)
				}
			} else {
				query, err = traffic.CleanQuery(query, t.Feeds[feedName])
				if err != nil {
					return fmt.Errorf("while cleaning query: %w", err)
				}
				lines, err1 := traffic.QueryLines(query, cfg.FeedsPath, feedName, versionCode, t)
				stops, err2 := traffic.QueryStops(query, context, t)
				if err1 != nil && err2 != nil {
					return fmt.Errorf("while querying stops and lines: %w", errors.Join(err1, err2))
				}
				items := []traffic.Queryable{}
				for _, line := range lines {
					items = append(items, line)
				}
				for _, stop := range stops {
					items = append(items, stop)
				}

				if _, ok := accept[0]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryablesV4(query, items, context, t, queryablesSuccess, false)
				} else if _, ok := accept[4]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryablesV4(query, items, context, t, queryablesSuccess, false)
				} else if _, ok := accept[3]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryablesV3(query, items, context, t, queryablesSuccess, false)
				} else if _, ok := accept[2]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryablesV2(query, items, context, t, queryablesSuccess, false)
				} else if _, ok := accept[1]; ok {
					queryablesSuccess, err = api.CreateSuccessQueryables(items, context, t, queryablesSuccess, false)
				} else {
					return ServerError{
						code: http.StatusNotAcceptable,
					}
				}
				if err != nil {
					return fmt.Errorf("while creating stopsSuccess from lines and stops: %w", err)
				}
			}
		}
	}

	queryablesSuccess = api.LimitQueryables(queryablesSuccess, offset, limit)
	bytes, err := bare.Marshal(&queryablesSuccess)
	if err != nil {
		return fmt.Errorf("while marshaling: %w", err)
	}
	_, err = w.Write(bytes)
	if err != nil {
		return fmt.Errorf("while writing: %w", err)
	}
	return nil
}

func handleDepartures(w http.ResponseWriter, r *http.Request, feedName string, cfg config.Config, t *traffic.Traffic, accept map[uint]struct{}) error {
	err := r.ParseForm()
	if err != nil {
		return fmt.Errorf("while parsing form: %w", err)
	}
	code := r.Form.Get("code")
	if code == "" {
		return ServerError{
			code:  http.StatusBadRequest,
			field: "code",
			value: "EMPTY",
		}
	}
	dateString := r.Form.Get("date")
	line := r.Form.Get("line")
	lineID := r.Form.Get("lineID")
	limitString := r.Form.Get("limit")
	if limitString == "" {
		limitString = "12"
	}
	limit, err := strconv.ParseUint(limitString, 10, 0)
	if err != nil {
		return ServerError{
			code:  http.StatusBadRequest,
			field: "limit",
			value: limitString,
		}
	}
	offsetString := r.Form.Get("offset")
	if offsetString == "" {
		offsetString = "0"
	}
	offset, err := strconv.ParseUint(offsetString, 10, 0)
	if err != nil {
		return ServerError{
			code:  http.StatusBadRequest,
			field: "offset",
			value: offsetString,
		}
	}
	departuresType := traffic.DEPARTURES_FULL
	if dateString == "" {
		departuresType = traffic.DEPARTURES_HYBRID
	}

	versionCode, date, err := parseDate(dateString, feedName, t)
	if err != nil {
		return err
	}

	context := traffic.Context{
		DataHome: cfg.FeedsPath,
		FeedID:   feedName,
		Version:  versionCode,
	}

	ix := t.CodeIndexes[feedName][versionCode]

	_, exists := ix[code]
	if !exists {
		return ServerError{
			code:  http.StatusNotFound,
			field: "code",
			value: string(code),
		}
	}

	stop, err := traffic.GetStop(code, context, t)
	if err != nil {
		return fmt.Errorf("while getting stop: %w", err)
	}

	if lineID == "" {
		l, _ := traffic.GetLineOld(line, context, t)
		lineID = l.Id
	}
	acceptLanguage := r.Header.Get("Accept-Language")
	if acceptLanguage == "" {
		acceptLanguage, err = traffic.GetLanguage(context)
		if err != nil {
			log.Printf("while gettng default language: %v\n", err)
			acceptLanguage = "und"
		}
	}
	preferredLanguages, _, err := language.ParseAcceptLanguage(acceptLanguage)
	if err != nil {
		return ServerError{
			code:  http.StatusBadRequest,
			field: "Accept-Language",
			value: acceptLanguage,
			err:   err,
		}
	}

	departures, err := traffic.GetDepartures(code, lineID, context, t, date, departuresType, preferredLanguages)
	if err != nil {
		if _, ok := err.(traffic_errors.NoSchedule); ok {
			return ServerError{
				code:  http.StatusNotFound,
				field: "date",
				value: dateString,
			}
		} else {
			return fmt.Errorf("while getting departures: %w", err)
		}
	}

	if departuresType == traffic.DEPARTURES_HYBRID {
		if int(offset) > len(departures) {
			departures = []traffic.DepartureRealtime{}
		} else if len(departures) < int(offset+limit) {
			departures = departures[offset:]
		} else {
			departures = departures[offset : offset+limit]
		}
	}

	stopAlerts := traffic.GetAlerts(stop.Id, stop.Code, -1, context, t, preferredLanguages)
	if err != nil {
		return fmt.Errorf("while getting alerts: %w", err)
	}

	var success api.DeparturesResponse
	if _, ok := accept[0]; ok {
		success, err = api.CreateSuccessDeparturesDev(stop, departures, date, t.Vehicles[feedName][versionCode], stopAlerts, context, t, accept, preferredLanguages)
	} else if _, ok := accept[4]; ok {
		success, err = api.CreateSuccessDeparturesV4(stop, departures, date, t.Vehicles[feedName][versionCode], stopAlerts, context, t, accept, preferredLanguages)
	} else if _, ok := accept[3]; ok {
		success, err = api.CreateSuccessDeparturesV3(stop, departures, date, t.Vehicles[feedName][versionCode], stopAlerts, context, t, accept, preferredLanguages)
	} else if _, ok := accept[2]; ok {
		success, err = api.CreateSuccessDeparturesV2(stop, departures, date, t.Vehicles[feedName][versionCode], stopAlerts, context, t, accept, preferredLanguages)
	} else if _, ok := accept[1]; ok {
		success, err = api.CreateSuccessDeparturesV1(stop, departures, date, t.Vehicles[feedName][versionCode], stopAlerts, context, t, accept, preferredLanguages)
	} else {
		return ServerError{
			code: http.StatusNotAcceptable,
		}
	}
	if err != nil {
		return fmt.Errorf("while creating departuresSuccess: %w", err)
	}
	bytes, err := bare.Marshal(&success)
	if err != nil {
		return fmt.Errorf("while marshaling: %w", err)
	}
	_, err = w.Write(bytes)
	if err != nil {
		return fmt.Errorf("while writing: %w", err)
	}
	return nil
}

func handleTrip(w http.ResponseWriter, r *http.Request, feedName string, cfg config.Config, t *traffic.Traffic, accept uint) error {
	path := strings.Split(r.URL.Path[1:], "/")
	if len(path) == 3 {
		dateString := r.Form.Get("date")
		versionCode, _, err := parseDate(dateString, feedName, t)
		if err != nil {
			return err
		}
		tripID := path[2]
		stopCode := r.Form.Get("stop")

		context := traffic.Context{
			DataHome: cfg.FeedsPath,
			FeedID:   feedName,
			Version:  versionCode,
		}

		trip, err := traffic.GetTripFromStop(tripID, stopCode, context, t)
		if err != nil {
			return fmt.Errorf("while getting line: %w", err)
		}
		if len(trip) == 0 {
			return ServerError{
				code:  http.StatusNotFound,
				field: "line",
				value: tripID,
			}
		}

		success := struct{}{} //api.CreateSuccessTrip(trip)
		bytes, err := bare.Marshal(&success)
		if err != nil {
			return fmt.Errorf("while marshaling trip: %w", err)
		}
		_, err = w.Write(bytes)
		if err != nil {
			return fmt.Errorf("while writing: %w", err)
		}
	} else {
		return ServerError{
			code:  http.StatusNotFound,
			field: "trip",
			value: "EMPTY",
		}
	}
	return nil
}

func sendError(w http.ResponseWriter, r *http.Request, err error) {
	var (
		se       ServerError
		response api.ErrorResponse
	)
	if !errors.As(err, &se) {
		se = ServerError{
			code: http.StatusInternalServerError,
			err:  err,
		}
	}
	response = api.ErrorResponse{
		Field:   se.field,
		Message: se.Error(),
	}
	log.Println(err.Error())
	b, err := bare.Marshal(&response)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.WriteHeader(se.code)
	w.Write(b)
}

func Route(cfg config.Config, traffic *traffic.Traffic) *http.Server {
	srv := &http.Server{Addr: cfg.ListenAddress}
	http.DefaultServeMux = &http.ServeMux{}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s?%s\n", r.Method, r.URL.RawPath, r.URL.RawQuery)
		accept, err := parseAccept(r.Header.Values("Accept"))
		if err != nil {
			sendError(w, r, fmt.Errorf("while parsing accept: %w", err))
			return
		}
		if r.URL.Path[1:] == "" {
			err = handleFeeds(w, r, cfg, traffic, accept)
		} else {
			path := strings.Split(r.URL.Path[1:], "/")
			feedNames := strings.Split(path[0], ",")
			for _, feedName := range feedNames {
				if traffic.Versions[feedName] == nil {
					sendError(w, r, ServerError{
						code:  http.StatusNotFound,
						field: "feed",
						value: feedName,
					})
					return
				}
			}

			if len(path) == 1 {
				if len(feedNames) > 1 {
					err = ServerError{
						code:  http.StatusBadRequest,
						field: "feed",
						value: path[0],
					}
				} else {
					err = handleFeed(w, r, feedNames[0], cfg, traffic, accept)
				}
			} else {
				resource := path[1]
				switch resource {
				case "queryables":
					err = handleQueryables(w, r, feedNames, cfg, traffic, accept)
				case "locatables":
					err = handleLocatables(w, r, feedNames, cfg, traffic, accept)
				case "departures":
					if len(feedNames) > 1 {
						err = ServerError{
							code:  http.StatusBadRequest,
							field: "feed",
							value: path[0],
						}
						break
					}
					err = handleDepartures(w, r, feedNames[0], cfg, traffic, accept)
				case "lines":
					if len(feedNames) > 1 {
						err = ServerError{
							code:  http.StatusBadRequest,
							field: "feed",
							value: path[0],
						}
						break
					}
					err = handleLine(w, r, feedNames[0], cfg, traffic, accept)
				/*case "trips":
				if len(feedNames) > 1 {
					err = ServerError{
						code:  http.StatusBadRequest,
						field: "feed",
						value: path[0],
					}
					break
				}
				err = handleTrip(w, r, feedNames[0], cfg, traffic)*/
				// todo(BAF21, BAF11): "shape" (line_id/trip_id)
				default:
					err = ServerError{
						code:  http.StatusNotFound,
						field: "resource",
						value: resource,
					}
				}
			}
		}
		if err != nil {
			sendError(w, r, err)
		}
	})

	go func() {
		if err := srv.ListenAndServe(); err != http.ErrServerClosed {
			log.Printf("ListenAndServe(): %v\n", err)
			os.Exit(1)
		}
	}()
	return srv
}
